package com.ndp.fb.walle.business.impl;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import com.ndp.ec.core.api.GoogleGaService;
import com.ndp.fb.config.ConfigCentre;
import com.ndp.fb.constant.ConfigConst;
import com.ndp.fb.enums.KeysConstant;
import com.ndp.fb.enums.KeysConstant.FbColumnKey;
import com.ndp.fb.exception.ga.NoGaProfileIdException;
import com.ndp.fb.model.report.param.EcReportInputFilterParam;
import com.ndp.fb.mongo.api.AdGroupMongoService;
import com.ndp.fb.mongo.model.AdForMongo;
import com.ndp.fb.rdb.api.EcTrackingProfilesRdbService;
import com.ndp.fb.rdb.api.EntrustAccountRdbService;
import com.ndp.fb.rdb.model.EntrustAccount;
import com.ndp.fb.util.DateUtil;
import com.ndp.fb.walle.business.FetchGoogleGaDataService;
import com.ndp.fb.walle.util.MapUtils;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ndp.fb.enums.KeysConstant.FbColumnKey.age;
import static com.ndp.fb.enums.KeysConstant.FbColumnKey.cpc;
import static com.ndp.fb.enums.KeysConstant.FbColumnKey.getFbConstant;

/**
 * Created by infi.he on 2016/3/17.
 */
@Component
public class FetchGoogleGaDataServiceImpl implements FetchGoogleGaDataService {

    private static Logger logger = LoggerFactory.getLogger(FetchGoogleGaDataServiceImpl.class);

    @Reference
    GoogleGaService googleGaService;
    @Reference
    private AdGroupMongoService adGroupMongoService;
    @Reference
    private EntrustAccountRdbService entrustAccountRdbService;
    @Reference
    private EcTrackingProfilesRdbService ecTrackingProfilesRdbService;

    @Override
    public List queryGaData(Map query) throws Exception {
        logger.info("accept query GA Data query>>>>" + JSON.toJSONString(query));
        List<Map<String, Object>> fbData = (List<Map<String, Object>>) query.get("list");
        List<String> groups = (List<String>) query.get("groups");
        Set<String> adNames = (Set<String>) query.get("adNames");

        Set<String> allAdNames = (Set<String>) query.get("allAdNames");
        Set<String> allAdIds = (Set<String>) query.get("allAdIds");
        String productId = query.get("productManageProductId").toString();
        Long orgId = (Long) query.get("orgId");
        EcReportInputFilterParam filters = (EcReportInputFilterParam) query.get("filters");
        String since = filters.getDay().getSince();
        String until = filters.getDay().getUntil();
        List list = queryAllGaProfileAndMixFbDataAndGaData(groups, Long.valueOf(productId), orgId, since, until, adNames, fbData, allAdNames, (int) query.get("allSpend"));
        return list;
    }


    public List queryAllGaProfileAndMixFbDataAndGaData(List<String> groups, Long productId, Long orgId, String startDate, String endDate,
                                                       Set<String> adNames, List<Map<String, Object>> fbData, Set<String> allAdNames, int allSpend)
            throws Exception {

        Set<FbColumnKey> dimensionsSet = Sets.newHashSet();

        List<String> metrics;
        Set<String> filterKeys = Sets.newHashSet();
        if (groups.contains("productId")) {
            metrics = initSkuMetricsList();
        } else {
            metrics = initMetricsList();
        }
        for (String group : groups) {
            if (KeysConstant.GA_GROUP_FIELDS_MAP.containsKey(group)) {
                FbColumnKey fbColumnKey = KeysConstant.GA_GROUP_FIELDS_MAP.get(group);
                dimensionsSet.add(fbColumnKey);
                filterKeys.add(fbColumnKey.getGakey());
            }
            //特殊分组条件，一般是ad的业务数据相关，必须 广告系列、出价方式等
            else if (ArrayUtils.contains(KeysConstant.GA_GROUP_ADNAMES_CONVERTE_DIMENSIONS, group)) {
                dimensionsSet.add(FbColumnKey.adNames);
                filterKeys.add(FbColumnKey.adNames.getGakey());
            }

            //特殊分组条件,sku
            else if (ArrayUtils.contains(KeysConstant.GA_GROUP_SKU_CONVERTE_DIMENSIONS, group)) {
                dimensionsSet.add(FbColumnKey.productId);
                filterKeys.add(FbColumnKey.productId.getGakey());
            } else {
                logger.error("ignore group key >>>" + group + " ,, add KeysConstant.GA_GROUP_FIELDS_MAP");
            }
        }

        List<Map<String, Object>> gaData = queryAllGaProfile(productId, orgId, startDate, endDate, adNames, dimensionsSet, metrics, filterKeys,false);
        //mix ga fb


        List<Map<String, Object>> result = fbData;
        if (gaData != null) {
            logger.info("mix fb and ga data");
            result = mixFbDataAndGaData(fbData, gaData, dimensionsSet);
        }

        //总数

        List<Map<String, Object>> gaSummary = queryAllGaProfile(productId, orgId, startDate, endDate, allAdNames,dimensionsSet , metrics, filterKeys,true);

        Map<String, Object> gaSummaryMap = new HashedMap();
        //将ga的名称换成 fb的名称 这样显示统一
        Map<String, Object> stringObjectMap = gaSummary.get(0);
        Map<String, Object> gaMap = new HashedMap();
        ConverterKey(stringObjectMap, gaMap);
        try {
            double roi = 0.0;
            double orderAmount = (double) gaMap.get("orderAmount");
            if (0 != allSpend && orderAmount > 0) {
                roi = orderAmount / allSpend;
            }
            double cpc = allSpend;
            int clicks = (int) gaMap.get("clicks");
            if (0 != clicks) {
                cpc = allSpend * 1.0 / clicks;
            }
            double cpa = allSpend;
            int pays = (int) gaMap.get("pays");
            if (0 != pays) {
                cpa = allSpend * 1.0 / pays;
            }
            double cr = 0.0;
            if (0 != pays && 0 != clicks) {
                cr = pays * 1.0 / clicks;
            }
            double cartCR = 0.0;
            int cart = (int) gaMap.get("cart");
            if (0 != cart && 0 != clicks) {
                cartCR = cart * 1.0 / clicks;
            }
            gaMap.put("roi", roi);
            gaMap.put("cpc", cpc);
            gaMap.put("cpa", cpa);
            gaMap.put("cr", cr);
            gaMap.put("cartCR", cartCR);
        } catch (Exception e) {
            logger.error("计算GA统计行指标出错,GA统计行数据：" + gaMap, e);
        }
        gaSummaryMap.put("gaSummary", gaMap);

        result.add(gaSummaryMap);
        return result;

    }

    private List<Map<String, Object>> queryAllGaProfile(Long productId, Long orgId, String startDate, String endDate, Set<String> adNames,
                                                        Set<FbColumnKey> dimensionsSet, List<String> metrics, Set<String> filterKeys,boolean isTotal) throws Exception {

        Map<FbColumnKey, Object> segmentMap = new HashMap<>();

        List<List<Map<String, Object>>> allGaData = new ArrayList<>();

        //        productId = 16091448714244L;  test
        //按照时区处理  set中包含id和name
        Map<String, Set<String>> timezoneAdNameMap = getTimezoneAdNameMap(adNames);

        queryGAData(productId, orgId, startDate, endDate, adNames, metrics, dimensionsSet, segmentMap, filterKeys, allGaData, timezoneAdNameMap);

        //合并所有id和name
        Set<String> adNameAndIds = new HashSet<>();
        for (Map.Entry<String, Set<String>> stringSetEntry : timezoneAdNameMap.entrySet()) {
            adNameAndIds.addAll(stringSetEntry.getValue());
        }

        int allSize = allGaData.stream().mapToInt(List::size).reduce((a, b) -> a + b).orElse(0);
        logger.info("fetch GA ok ,productId:{}, orgId:{},size:{}", productId, orgId, allSize);
        List<List<Map<String, Object>>> filteredGaData = allGaData;
        if (dimensionsSet.contains(FbColumnKey.adNames)) {
            Set<String> adNames1 = adNameAndIds.stream().map(d -> {
                return d.toLowerCase();
            }).collect(Collectors.toSet());
            //根据adName过滤 初步过滤出来
            filteredGaData = allGaData.stream().map(d -> {
                return d.stream().filter(g -> {
                    String keyword = (String) g.get("keyword");
                    if (StringUtils.isNoneBlank(keyword)) {
                        boolean isFound = false;
                        for (String adName : adNames1) {
                            isFound = isFound || keyword.contains(adName);
                        }

                        return isFound;
                    } else {
                        return false;
                    }
                }).collect(Collectors.toList());
            }).collect(Collectors.toList());
        }


        int filteredSize = filteredGaData.stream().mapToInt(List::size).reduce((a, b) -> a + b).orElse(0);
        logger.info("filter GA data by AdName,resultSize:{}", filteredSize);
        //merge ga data
        List<Map<String, Object>> gaData = null;
        if (isTotal){
            Set<String> tempFilterKeys = new HashSet<>();
            tempFilterKeys.add(FbColumnKey.day.getGakey());
            List<Map<String, Object>>  tempGaData = MapUtils.mergeMap(filteredGaData, tempFilterKeys);

            /////// 将所选的天 合并
            Map<String, Object> existsMap = new HashedMap();
            for (Map<String, Object> stringObjectMap : tempGaData) {
                for (Map.Entry<String, Object> stringObjectEntry : stringObjectMap.entrySet()) {
                    String key = stringObjectEntry.getKey();
                    Object value = stringObjectEntry.getValue();
                    String name = value.getClass().getSimpleName();
                    switch (name) {
                        case "Integer":
                            Integer integer = (Integer) existsMap.get(key);
                            Integer gaValue = (Integer) value;
                            Integer re = (integer == null ? 0 : integer) + (gaValue == null ? 0 : gaValue);
                            existsMap.put(key, re);
                            break;
                        case "Double":
                            Double Doubleinteger = (Double) existsMap.get(key);
                            Double DoublegaValue = (Double) value;
                            Double Doublere = (Doubleinteger == null ? 0 : Doubleinteger) + (DoublegaValue == null ? 0 : DoublegaValue);
                            existsMap.put(key, Doublere);
                            break;
                        case "Float":
                            Float Floatinteger = (Float) existsMap.get(key);
                            Float FloatgaValue = (Float) value;
                            Float Floatre = (Floatinteger == null ? 0 : Floatinteger) + (FloatgaValue == null ? 0 : FloatgaValue);
                            existsMap.put(key, Floatre);
                            break;
                        default:
                            break;
                    }
                }
            }

            tempGaData.clear();
            tempGaData.add(existsMap);
            gaData = tempGaData;

        } else {
            gaData = MapUtils.mergeMap(filteredGaData, filterKeys);
        }
        logger.info("merge ga data ok");
        return gaData;
    }

    private void queryGAData(Long productId, Long orgId, String startDate, String endDate, Set<String> adNames, List<String> metrics, Set<FbColumnKey> dimensionsSet,
                             Map<FbColumnKey, Object> segmentMap, Set<String> filterKeys, List<List<Map<String, Object>>> allGaData,
                             Map<String, Set<String>> timezoneAdNameMap) throws Exception {
        for (String t : timezoneAdNameMap.keySet()) {
            List<Long> profileIds = ecTrackingProfilesRdbService.findProfileIdsByProductIdAndTimeZone(productId, DateUtil.timezoneToOffSite(t));
            if (!profileIds.isEmpty()) {
                for (Long p : profileIds) {
                    try {
                        List<Map<String, Object>> gaData = manyBatchGaQuery(productId, orgId, p, startDate, endDate, adNames, metrics, dimensionsSet, segmentMap, filterKeys);
                        allGaData.add(gaData);
                    } catch (IOException e) {
                        logger.error("queryGaProfile Error.", e.getMessage());
                    }
                }
            } else {
                logger.info(" profile id is null,go to ec_ga_credential table set. productId:{}", productId);
                throw new NoGaProfileIdException(productId + " profile id is null,go to ec_ga_credential table set");
            }
        }
    }

    /**
     * GA_GROUP_FIELDS_MAP
     * fbFilterMap value :
     * {
     * adnames : [
     * adid,ad,,....
     * ]
     * device:"1231"
     * }
     *
     * @param gaDataList
     * @param fbFilterMap
     * @return
     */
    private List<Map<String, Object>> getGaDataMapByFbFilterMap(List<Map<String, Object>> gaDataList,
                                                                Map<FbColumnKey, Object> fbFilterMap, Map<String, Object> fbDataMap) {
        List<Map<String, Object>> tmpResultList = new ArrayList<>();
        for (Map<String, Object> map : gaDataList) {
            boolean isFound = true;
            for (Map.Entry<FbColumnKey, Object> fbColumnKeyObjectEntry : fbFilterMap.entrySet()) {
                FbColumnKey key = fbColumnKeyObjectEntry.getKey();
                Object fbValue = fbColumnKeyObjectEntry.getValue();
                Object gaValue = map.get(key.getGakey());
                switch (key) {
                    case day:
                        DateTime dt1 = new DateTime(fbValue);
                        String fbKeyValue = dt1.toString("yyyyMMdd");
                        gaValue = gaValue.toString();
                        isFound = isFound && com.google.common.base.Objects.equal(fbKeyValue, gaValue);
                        break;
                    case adNames:

                        //先用adId匹配，匹配不到使用adname
                        List idsList = (List<String>) fbDataMap.get("adIds");
                        List<String> adNamesList = (List<String>) fbDataMap.get("adNames");

                        List<String> list = new ArrayList<>();
                        if (idsList != null) {
                            for (Object o : idsList) {
                                list.add(o.toString());
                            }
                        }

                        if (adNamesList != null) list.addAll(adNamesList);

                        boolean tempB = false;
                        for (String s : list) {
                            tempB = tempB || gaValue.toString().toUpperCase().endsWith(s.toUpperCase());
                        }
                        isFound = tempB & isFound;
                        break;
                    default:
                        gaValue = gaValue.toString();
                        isFound = isFound && com.google.common.base.Objects.equal(fbValue, gaValue);
                }
            }

            if (isFound) {
                tmpResultList.add(map);
            }
        }
        return tmpResultList;
    }


    private List<Map<String, Object>> manyBatchGaQuery(Long productId, Long orgId, Long profileId, String startDate, String endDate,
                                                       Set<String> adNames, List<String> metrics,
                                                       Set<FbColumnKey> dimensionsSet, Map<FbColumnKey, Object> segmentMap, Set<String> filterKeys) throws Exception {
        //判断是否查询总数据
        List<List<Map<String, Object>>> gaResultList = new ArrayList<>();
        if (filterKeys.size() == 1 && filterKeys.contains(FbColumnKey.day.getGakey())) {
            List<Map<String, Object>> maps = googleGaService.queryGaDataFilterAd(orgId, productId, profileId, startDate, endDate, metrics, dimensionsSet, segmentMap);
            gaResultList.add(maps);
        }
        //是使用ad相关业务数据分组
        else if (dimensionsSet.contains(FbColumnKey.adNames)) {
            List<Map<String, Object>> maps = googleGaService.queryGaDataFilterAd(orgId, productId, profileId, startDate, endDate, metrics, dimensionsSet, segmentMap);
            gaResultList.add(maps);
        }
        //细分数据需要增加adNames
        else {
            int i = 0;
            List<String> tmpAdName = new ArrayList<>();
            for (String adName : adNames) {
                tmpAdName.add(adName);

                if (i + 1 == adNames.size() || tmpAdName.size() == ConfigCentre.getInteger(ConfigConst.GA_BATCH_FETCH_ADNAME_SIZE, 30)) { //最有一次
                    segmentMap.put(FbColumnKey.adNames, tmpAdName);
                    List<Map<String, Object>> maps = googleGaService.queryGaDataFilterAd(orgId, productId, profileId, startDate, endDate, metrics, dimensionsSet, segmentMap);
                    gaResultList.add(maps);
                    tmpAdName.clear();
                }
                i++;
            }
        }

        logger.info("merge ga data");
        List<Map<String, Object>> gaData = MapUtils.mergeMap(gaResultList, filterKeys);
        return gaData;
    }

    /**
     * 返回时区与ad的对应关系
     *
     * @param adNames
     * @return
     */
    private Map<String, Set<String>> getTimezoneAdNameMap(Set<String> adNames) {
        List<AdForMongo> ads = adGroupMongoService.findByNameIn(adNames);
        // 时区 1：n adName
        Map<String, Set<String>> timezoneAdNameMap = new HashMap<>();
        // 账号1：n ads
        Map<Long, List<AdForMongo>> actAdsMap = ads.stream()
                .collect(Collectors.groupingBy(AdForMongo::getAccount_id));
        actAdsMap.keySet().forEach(act -> {
            EntrustAccount entrustAccount = entrustAccountRdbService.findbyEntrustAccountId(act, 0);
            Set<String> names = timezoneAdNameMap.get(entrustAccount.getTimezone()) == null ? new HashSet<>() : timezoneAdNameMap.get(entrustAccount.getTimezone());
            //adNames
            Set<String> n = actAdsMap.get(act).stream().map(AdForMongo::getName).collect(Collectors.toSet());

            Set<String> ids = actAdsMap.get(act).stream().map(adForMongo -> adForMongo.getAd_id().toString()).collect(Collectors.toSet());

            names.addAll(n);
            names.addAll(ids);
            timezoneAdNameMap.put(entrustAccount.getTimezone(), names);
        });
        return timezoneAdNameMap;
    }


    private List mixFbDataAndGaData(List<Map<String, Object>> fbData, List<Map<String, Object>> gaDataList, Set<FbColumnKey> dimensionsSet) {
        for (Map<String, Object> map : fbData) {
            Map<FbColumnKey, Object> fbFilterMap = new HashMap<>();
            for (FbColumnKey fbColumnKey : dimensionsSet) {
                for (Map.Entry<String, FbColumnKey> stringFbColumnKeyEntry : KeysConstant.GA_GROUP_FIELDS_MAP.entrySet()) {
                    String key = stringFbColumnKeyEntry.getKey();
                    FbColumnKey value = stringFbColumnKeyEntry.getValue();
                    if (value == fbColumnKey || fbColumnKey.equals(value)) {
                        fbFilterMap.put(fbColumnKey, map.get(key));
                    }
                }
            }

            List<Map<String, Object>> gaMapList = getGaDataMapByFbFilterMap(gaDataList, fbFilterMap, map);
            Map<String, Object> gaMap = null;
            //如果包含业务数据，需要进行2次合并处理
            if (dimensionsSet.contains(FbColumnKey.adNames) && dimensionsSet.size() > 1) {
                Set<String> filterKeys = Sets.newHashSet();
                for (FbColumnKey fbColumnKey : dimensionsSet) {
                    if (FbColumnKey.adNames == fbColumnKey || com.google.common.base.Objects.equal(fbColumnKey, FbColumnKey.adNames)) {
                        continue;
                    } else {
                        filterKeys.add(fbColumnKey.getGakey());
                    }
                }
                gaMapList = MapUtils.mergeMapList(gaMapList, filterKeys);
            }

            if (gaMapList.size() > 0) {
                gaMap = gaMapList.get(0);
            } else {
                gaMap = null;
            }

            Integer spend = (Integer) map.get("spend");
            spend = spend == null ? 0 : spend;
            Map result = new HashMap<>();
            if (spend > 0 && gaMap != null && gaMap.size() > 0) { //过滤一次空数据
                double roi = 0;
                double cartCR = 0;
                double orderPrice = 0;

                Integer sessions = (Integer) gaMap.get(FbColumnKey.clicks.getGakey());
                sessions = sessions == null ? 0 : sessions;
                Integer cart = (Integer) gaMap.get(FbColumnKey.cart.getGakey());
                cart = cart == null ? 0 : cart;
                Integer pays = (Integer) gaMap.get(FbColumnKey.pays.getGakey());
                pays = pays == null ? 0 : pays;
                Double orderAmount = (Double) gaMap.get(FbColumnKey.orderAmount.getGakey());
                orderAmount = orderAmount == null ? 0 : orderAmount;

                roi = orderAmount / spend;
                if (cart != null && cart > 0) {
                    cartCR = cart == 0 ? 0 : cart / (double) sessions;
                }
                orderPrice = (orderAmount == 0 || pays == 0) ? 0 : orderAmount / (double) pays;

                double cpc = sessions == 0 ? 0 : spend / (double) sessions;
                double cpa = pays == 0 ? 0 : spend / (double) pays;
                double cr = pays == 0 ? 0 : pays / (double) sessions;

                result.put("roi", roi);
                result.put("cpc", cpc);
                result.put("cpa", cpa);
                result.put("cr", cr);
                result.put("cartCR", cartCR);
                result.put("orderPrice", orderPrice);
                ConverterKey(gaMap, result);
            }

            map.put("gaData", result);
        }
        return fbData;
    }


    private Map<String, Object> getDataByGakey(List<Map<String, Object>> mergeData, String gakey) {
        Map<String, Object> found = null;
        for (Map<String, Object> map : mergeData) {
            if (map.containsKey(gakey)) {
                found = map;
                break;
            }
        }
        return found;
    }

    /**
     * 匹配到对应的值
     *
     * @param dimensionsMap
     * @param maps
     * @return
     */
    private Map<String, Object> filterResultMap(Map<FbColumnKey, Object> dimensionsMap, List<Map<String, Object>> maps) {
        if (maps == null || maps.size() == 0) return null;
        for (Map<String, Object> map : maps) {
            boolean isFound = true;
            for (Map.Entry<FbColumnKey, Object> fbColumnKeyObjectEntry : dimensionsMap.entrySet()) {
                FbColumnKey key = fbColumnKeyObjectEntry.getKey();
                String gaKey = key.getGakey();
                Object fbValue = fbColumnKeyObjectEntry.getValue();
                Object gaValue = map.remove(gaKey);
                if (key == age) {
                    String tmpFbValue = ((String) fbValue).substring(0, 2);
                    String tmpGaValue = ((String) gaValue).substring(0, 2);
                    isFound = isFound && tmpFbValue.equals(tmpGaValue);
                } else {
                    isFound = isFound && com.google.common.base.Objects.equal(fbValue, gaValue);
                }
            }

            if (isFound) {
                return map;
            }
        }
        return new HashMap<>();
    }

    private List<String> initMetricsList() {
        List<String> metrics = new ArrayList<>();
        metrics.add("revenuePerTransaction");
        metrics.add("transactions"); // 交易单数
        metrics.add("transactionRevenue"); //交易金额
//        metrics.add("transactionsPerSession");
        metrics.add("sessions");  //点击数
        metrics.add("goal1Completions"); //加入购物车
//        metrics.add("goal1ConversionRate");
        return metrics;
    }


    private List<String> initSkuMetricsList() {
        List<String> metrics = new ArrayList<>();
        metrics.add("uniquePurchases");
        metrics.add("itemRevenue");  //产品销售额
        metrics.add("itemQuantity"); //产品销售数量
        return metrics;
    }

    private void ConverterKey(Map map, Map result) {
        map.entrySet().forEach(new Consumer<Map.Entry>() {
            @Override
            public void accept(Map.Entry entry) {
                String key = entry.getKey().toString();
                Object value = entry.getValue();
                FbColumnKey fbConstant = getFbConstant(key);
                if (null == fbConstant) {
                    if (!result.containsKey(key)) result.put(key, value);
                } else {
                    String s = fbConstant.toString();
                    if (!result.containsKey(s)) result.put(s, value);
                }
            }
        });
    }
}
